package xperiment.metaphor.model;

import java.io.Serializable;
import java.util.List;
import javax.persistence.Transient;
import metaphor.service.ApplicationManager;
import metaphor.service.ServiceException;
import metaphor.service.NoApplicationManagerException;
import metaphor.service.SecurityGranted;
import metaphor.persistence.Dao;
import metaphor.persistence.filter.Query;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.acls.sid.Sid;
import org.springframework.security.acls.sid.PrincipalSid;
import org.springframework.security.acls.sid.GrantedAuthoritySid;
import org.springframework.security.acls.Permission;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import metaphor.persistence.filter.SearchCriteria;
import metaphor.persistence.filter.SimpleExpression;

/**
 * Domain Object
 * 
 * @author Mark Ashworth
 * @version 1.0.0
 * @since 1.0.0
 */
public abstract class DomainObject extends AbstractBean implements Persistent {
    private static final long serialVersionUID = 1L;

    @Transient
    private ApplicationManager applicationManager;

    @Transient
    protected Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * Attaches the application manager
     * @param applicationManager The new value
     */
    public void attach(ApplicationManager applicationManager) {
        this.applicationManager = applicationManager;
    }

    /**
     * Returns the application manager
     * @return ApplicationManager
     */
    public ApplicationManager getApplicationManager() {
        return applicationManager;
    }
    
    /**
     * Returns the beans defined by the name
     * @param name The name of the bean
     * @return Object
     * @throws ServiceException If the application manager was not attached
     */
    protected Object getBean(String name) throws ServiceException {
        if (applicationManager == null) throw new NoApplicationManagerException("The applicationContext has not been attached or is null");
        return applicationManager.getBean(name);
    }

    /**
     * Returns the beans defined by the name
     * @param name The name of the bean
     * @param beanClass The class of the bean
     * @return Object
     * @throws ServiceException If the application manager was not attached
     */
    protected Object getBean(String name, Class beanClass) throws ServiceException {
        if (applicationManager == null) throw new NoApplicationManagerException("The applicationContext has not been attached or is null");
        return applicationManager.getBean(name, beanClass);
    }
    
    /**
     * Returns the Dao defined by the name BeanNames.DAO
     * @return Dao
     * @throws ServiceException If the application manager was not attached
     */
    protected Dao getDao() throws ServiceException {
        if (applicationManager == null) throw new NoApplicationManagerException("The applicationContext has not been attached or is null");
        return (Dao) applicationManager.getDao();
    }

    /**
     * Returns the SecurityManager defined by the name BeanNames.SECURITY_MANAGER
     * @return metaphor.service.SecurityManager
     * @throws ServiceException If the application manager was not attached
     */
    protected metaphor.service.SecurityManager getSecurityManager() throws ServiceException {
        if (applicationManager == null) throw new NoApplicationManagerException("The applicationContext has not been attached or is null");
        return applicationManager.getSecurityManager();
    }

    /**
     * Retrieves the objects from the persistent store
     * @param applicationManager The application manager
     * @param persistentClass The persistent class to retrieve
     * @param all Whether all the objects should be included and not just enabled objects
     * @return List
     * @throws xperiment.metaphor.model.ModelException If the list of objects could not be retrieved
     */
    public static List retrieve(ApplicationManager applicationManager, Class persistentClass, boolean all) throws ModelException {

        try {
            if (applicationManager == null) throw new NoApplicationManagerException("The applicationContext is null");
            Dao dao = (Dao) applicationManager.getDao();
            Query query = Query.forClass(persistentClass);
            if (!all) {
                query.add(SimpleExpression.eq("enabled", Boolean.TRUE));
            }

            return dao.retrieve(query);
        } catch (ServiceException e) {
            throw new RetrieveException("Unable to retrieve", e);
        }
    }

    /**
     * Retrieves the objects by a search criteria
     * @param applicationManager The application manager
     * @param searchCriteria The search criteria
     * @return List
     * @throws xperiment.metaphor.model.ModelException If the list of objects could not be retrieved
     */
    public static List retrieve(ApplicationManager applicationManager, SearchCriteria searchCriteria) throws ModelException {

        try {
            if (applicationManager == null) throw new NoApplicationManagerException("The applicationContext is null");
            Dao dao = (Dao) applicationManager.getDao();
            return dao.retrieve(searchCriteria);
        } catch (ServiceException e) {
            throw new RetrieveException("Unable to retrieve users", e);
        }
    }

    /**
     * Retrieves the object by id
     * @param applicationManager The application manager
     * @param persistentClass The persistent class to retrieve
     * @param id The id of the user to retrieve
     * @return Object
     * @throws xperiment.metaphor.model.ModelException If the object could not be retrieved
     */
    public static Object retrieveById(ApplicationManager applicationManager, Class persistentClass, Serializable id) throws ModelException {

        try {
            if (applicationManager == null) throw new NoApplicationManagerException("The applicationContext is null");
            Dao dao = (Dao) applicationManager.getDao();
            return dao.retrieveById(persistentClass, id);
        } catch (ServiceException e) {
            throw new RetrieveException("Unable to retrieve object with id `" + id + "`", e);
        }
    }

    /**
     * Retrieves the users
     * @param persistentClass The persistent class to retrieve
     * @param all Whether all users should be included and not just enabled users
     * @return List<User>
     * @throws xperiment.metaphor.model.ModelException If the list of users could not be retrieved
     */
    public List retrieve(Class persistentClass, boolean all) throws ModelException {
        return retrieve(getApplicationManager(), persistentClass, all);
    }

    public List retrieve(SearchCriteria searchCriteria) throws ModelException {
        return retrieve(getApplicationManager(), searchCriteria);
    }

    /**
     * Grants read permission to the user on the current object.<br />
     *
     * @param sid The security identity to grant the permission to
     * @throws metaphor.service.SecurityException If there was an error
     */
    public void grantRead(
            Sid sid) throws metaphor.service.SecurityException {

        try {
            getSecurityManager().grantRead(getClass(), (Long) getId(), sid);
        } catch (ServiceException e) {
            throw new SecurityException("Unable to grant read permission", e);
        }
    }

    /**
     * Grants write permission to the user on the current object.<br />
     *
     * @param sid The security identity to grant the permission to
     * @throws metaphor.service.SecurityException If there was an error
     */
    public void grantWrite(
            Sid sid) throws metaphor.service.SecurityException {

        try {
            getSecurityManager().grantWrite(getClass(), (Long) getId(), sid);
        } catch (ServiceException e) {
            throw new SecurityException("Unable to grant write permission", e);
        }
    }

    /**
     * Grants create permission to the user on the current object.<br />
     *
     * @param sid The security identity to grant the permission to
     * @throws metaphor.service.SecurityException If there was an error
     */
    public void grantCreate(
            Sid sid) throws metaphor.service.SecurityException {

        try {
            getSecurityManager().grantCreate(getClass(), (Long) getId(), sid);
        } catch (ServiceException e) {
            throw new SecurityException("Unable to grant create permission", e);
        }
    }

    /**
     * Grants delete permission to the user on the current object.<br />
     *
     * @param sid The security identity to grant the permission to
     * @throws metaphor.service.SecurityException If there was an error
     */
    public void grantDelete(
            Sid sid) throws metaphor.service.SecurityException {

        try {
            getSecurityManager().grantDelete(getClass(), (Long) getId(), sid);
        } catch (ServiceException e) {
            throw new SecurityException("Unable to grant delete permission", e);
        }
    }

    /**
     * Grants administration permission to the user on the current object.<br />
     *
     * @param sid The security identity to grant the permission to
     * @throws metaphor.service.SecurityException If there was an error
     */
    public void grantAdministration(
            Sid sid) throws metaphor.service.SecurityException {

        try {
            getSecurityManager().grantAdministration(getClass(), (Long) getId(), sid);
        } catch (ServiceException e) {
            throw new SecurityException("Unable to grant administration permission", e);
        }
    }

    /**
     * Grants all permissions to the user on the current object.<br />
     *
     * @param sid The security identity to grant the permission to
     * @throws metaphor.service.SecurityException If there was an error
     */
    public void grantAll(
            Sid sid) throws metaphor.service.SecurityException {

        try {
            getSecurityManager().grantAll(getClass(), (Long) getId(), sid);
        } catch (ServiceException e) {
            throw new SecurityException("Unable to grant all permissions", e);
        }
    }

    /**
     * Grants a permission to the user on the current object.<br />
     *
     * @param sid The security identity to grant the permission to
     * @param permission The permission to grant to the user
     * @throws metaphor.service.SecurityException If there was an error
     */
    public void grantPermission(
            Sid sid,
            Permission permission) throws metaphor.service.SecurityException {

        try {
            getSecurityManager().grantPermission(getClass(), (Long) getId(), sid, permission);
        } catch (ServiceException e) {
            throw new SecurityException("Unable to grant all permissions", e);
        }
    }

    /**
     * Grants a set of permissions to the user on the current object.<br />
     *
     * @param sid The security identity of whom to grant access to
     * @param permissions The permissions to grant to the user
     * @throws metaphor.service.SecurityException If there was an error
     */
    public void grantPermission(
            Sid sid,
            Permission[] permissions) throws metaphor.service.SecurityException {

        try {
            getSecurityManager().grantPermission(getClass(), (Long) getId(), sid, permissions);
        } catch (ServiceException e) {
            throw new SecurityException("Unable to grant permissions", e);
        }
    }

    /**
     * Grants a permission to the user on the object defined by the persistentClass
     * and object identifier.<br />
     *
     * @param sid The security identity to grant the permission to
     * @param permission The permission to grant to the user
     * @throws metaphor.service.SecurityException If there was an error
     */
    public void denyPermission(
            Sid sid,
            Permission permission) throws metaphor.service.SecurityException {

        try {
            getSecurityManager().denyPermission(getClass(), (Long) getId(), sid, permission);
        } catch (ServiceException e) {
            throw new SecurityException("Unable to deny permission", e);
        }
    }

    /**
     * Change the owner of a persistent object.<br />
     * 
     * @param sid The security identity of the new owner
     * @throws metaphor.service.SecurityException If there was an error
     */
    public void changeOwnership(
            Sid sid) throws metaphor.service.SecurityException {

        try {
            getSecurityManager().changeOwnership(getClass(), (Long) getId(), sid);
        } catch (ServiceException e) {
            throw new SecurityException("Unable to change ownership", e);
        }
    }

    /**
     * Determines if the current authenticated user defined by SecurityContextHolder
     * has the permission on the current object.<br />
     *
     * @param permission The permission
     * @param administrativeMode Whether the query is done in administrator mode
     * @return SecurityGranted.GRANTED, DENIED, NOTFOUND
     * @throws metaphor.service.SecurityException If there was an error
     */
    public SecurityGranted isGranted(
            Permission permission,
            boolean administrativeMode) throws metaphor.service.SecurityException {

        return isGranted(new Permission[]{permission}, administrativeMode);
    }

    /**
     * Determines if the current authenticated user defined by SecurityContextHolder
     * has the permissions on the current object.<br />
     *
     * @param permissions The permissions
     * @param administrativeMode Whether the query is done in administrator mode
     * @return SecurityGranted.GRANTED, DENIED, NOTFOUND
     * @throws metaphor.service.SecurityException If there was an error
     */
    public SecurityGranted isGranted(
            Permission[] permissions,
            boolean administrativeMode) throws metaphor.service.SecurityException {

        return isGranted(SecurityContextHolder.getContext().getAuthentication(), permissions, administrativeMode);
    }

    /**
     * Determines whether the authentication object provides has the permissions
     * @param authentication The authentication object containing the principal and granted authorities
     * @param permission The permission
     * @param administrativeMode Whether the call is performed in administrative mode
     * @return SecurityGranted
     * @throws metaphor.service.SecurityException If there was an error
     */
    public SecurityGranted isGranted(
            Authentication authentication,
            Permission permission,
            boolean administrativeMode) throws metaphor.service.SecurityException {

        return isGranted(authentication, new Permission[]{permission}, administrativeMode);
    }

    /**
     * Determines whether the authentication object provides has the permissions
     * @param authentication The authentication object containing the principal and granted authorities
     * @param permissions The permissions
     * @param administrativeMode Whether the call is performed in administrative mode
     * @return SecurityGranted
     * @throws metaphor.service.SecurityException If there was an error
     */
    public SecurityGranted isGranted(
            Authentication authentication,
            Permission[] permissions,
            boolean administrativeMode) throws metaphor.service.SecurityException {

        Sid[] sids = new Sid[authentication.getAuthorities().length + 1];

        sids[0] = new PrincipalSid(authentication.getName());

        int i = 1;
        for (GrantedAuthority authority : authentication.getAuthorities()) {
            sids[i++] = new GrantedAuthoritySid(authority.getAuthority());
        }

        return isGranted(sids, permissions, administrativeMode);
    }

    /**
     * Determines whether the authentication object provides has the permissions
     * @param sids The security identifiers
     * @param permissions The permissions
     * @param administrativeMode Whether the call is performed in administrative mode
     * @return SecurityGranted
     * @throws metaphor.service.SecurityException If there was an error
     */
    public SecurityGranted isGranted(
            Sid[] sids,
            Permission[] permissions,
            boolean administrativeMode) throws metaphor.service.SecurityException {

        try {
            return getSecurityManager().isGranted(getClass(), (Long) getId(), permissions, sids, administrativeMode);
        } catch (ServiceException e) {
            throw new SecurityException("Unable to check if permissions are granted", e);
        }
    }
}
